import asyncio
import logging
from datetime import timedelta

from .tcp_client import ClientThread
from .crc16 import crc16_fn
from homeassistant.helpers.event import async_track_time_interval
_LOGGER = logging.getLogger(__name__)


class ModbusApp():
    """modbus客户端"""

    def __init__(self, host, port, hass, lights):
        self._lock = asyncio.Lock()
        self._poll = self._handle_slvaes_poll(lights)
        self._cb = []
        self.host = host
        self.port = port
        self._hass = hass
        self._write_cmd_obj = []
        self.sock_client = ClientThread(host,port)

    # 取出需要轮询的从机号及起始线圈地址
    def _handle_slvaes_poll(self, switchs):

        poll = {}
        # 分离每一个从机的线圈地址
        for conf in switchs:
            if conf['slave'] not in poll:
                poll[conf['slave']] = {'address': []}
            poll[conf['slave']]['address'].append(conf['address'])

        # 计算起始地址，及轮询数量
        for slave in poll:
            poll[slave]['start'] = min(poll[slave]['address'])
            poll[slave]['num'] = max(
                poll[slave]['address'])-min(poll[slave]['address'])+1

        return poll

    async def async_send(self,data):
        await self._hass.async_add_executor_job(self.sock_client.send_data,data)  
        '''try:
            coro = asyncio.open_connection(self.host, self.port)
            reader, writer = await asyncio.wait_for(coro, 1)
        except Exception as e:
            _LOGGER.error("TCP连接失败")
            return 
        writer.write(bytes(data))
        try:
            read = reader.read(20)
            buf = await asyncio.wait_for(read,0.5)         
            if len(buf)>2:
                self.onmessage(buf)
        except Exception as e:
            _LOGGER.error('接收超时')
        writer.close()'''

    async def req_state(self, slave):
        start = self._poll[slave]['start']
        num = self._poll[slave]['num']
        """请求状态"""
        data = [slave, 1, 0, start, 0, num]
        for crc in crc16_fn(data):
            data.append(crc)
        async with self._lock:
            await self.async_send(data)

    def handle_state(self, data):
        """处理状态"""
        if data[1] != 1:
            return False
        states = {}
        start = self._poll[data[0]]['start']
        for i in range(data[2]):
            bits = data[i+3]
            for j in range(8):
                states[start + j + i*8] = True if bits >> j & 0x01 else False
        if len(self._cb) > 0:
            for cb in self._cb:
                cb(states, data[0])
        

    async def poll_state(self, e):
        for slave in self._poll:
            '''if len(self._write_cmd_obj) > 0: #优先写命令
                await self.handle_write()
                await asyncio.sleep(0.05)'''

            await self.req_state(slave)#轮询状态
            await asyncio.sleep(0.05)

    async def stop(self):
        await self._hass.async_add_executor_job(self.sock_client.stop)

    async def start_hub(self) -> None:
        self.sock_client.set_call_fn(self.onmessage)
        await self._hass.async_add_executor_job(self.sock_client.start) 
        async_track_time_interval(
            self._hass, self.poll_state, timedelta(seconds=1)
        )

    def add_callback(self, cb):
        self._cb.append(cb)

    async def handle_write(self):
        #写第一组命令
        slave,address,cmd = self._write_cmd_obj[0]
        self._write_cmd_obj = self._write_cmd_obj[1:]
        """写入命令"""
        data = [slave, 5, 0, address,0,0]
        if cmd:
            data[4] = 0xff
        for crc in crc16_fn(data):
            data.append(crc)
        async with self._lock:
            await self.async_send(data)

    async def write_cmd(self, slave, address, cmd):
        #放入写入队列
        #self._write_cmd_obj.append((slave,address,cmd))
        data = [slave, 5, 0, address,0,0]
        if cmd:
            data[4] = 0xff
        for crc in crc16_fn(data):
            data.append(crc)
        async with self._lock:
            await self.async_send(data)
        

    def onmessage(self, data):
        if not self.check(data):
            """校验失败"""
            return False
        self.handle_state(data[:-2])

    def check(self, data):
        """CRC校验"""
        crc = crc16_fn(data[:-2])
        return (data[-2] == crc[0]) & (data[-1] == crc[1])
